package dao;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import bean.Customer;
import bean.Fund;
import bean.FundTransaction;
import bean.LoggedUser;
import bean.Position;
import bean.TransactionStatus;
import bean.UserBean;
import formBean.TransitionDayForm;

public class FundTransactionDAO extends DAO<UserBean> {
	private CustomerDAO customerDAO;
	private PositionDAO positionDAO;
	
	
	public FundTransactionDAO(SessionFactory factory, CustomerDAO customerDAO, PositionDAO positionDAO) {
		super(factory);
		this.customerDAO = customerDAO;
		this.positionDAO = positionDAO;
	}
	
	public boolean createFundTransaction(FundTransaction fundTransaction) {
		Transaction tx = null;
		Session session = null;
		try {				
			session = this.getSession();
			tx = session.beginTransaction();
			session.save(fundTransaction);
			tx.commit();
		} catch (HibernateException e) {
			if (tx != null)
				tx.rollback();
			e.printStackTrace();
			return false;
		} finally {
			if(session != null)	session.close();
		}
		
		return true;
	}
	
	public Date getLatestExecuteDate() {
		Session session = null;		
		try {
			session = this.getSession();
			Query qr = session.createQuery("select t.executeDate from FundTransaction t where t.executeDate is not null");
			List<Date> list = qr.list();
			if (list == null || list.size() == 0) return null;
			Collections.sort(list);
			return list.get(list.size() - 1);
		} catch (HibernateException e) {
			e.printStackTrace();
			return null;
		} 
	}
	
	public Date getLatestOrderDate() {
		Session session = null;		
		try {
			session = this.getSession();
			Query qr = session.createQuery("select t.orderDate from FundTransaction t where t.orderDate is not null");
			List<Date> list = qr.list();
			if (list == null || list.size() == 0) return null;
			Collections.sort(list);
			return list.get(list.size() - 1);
		} catch (HibernateException e) {
			e.printStackTrace();
			return null;
		} 
	}
	
	public List getTransactionByCustomerId(String customerId){
		List transactions = this.getByAttribute("customerId", customerId, FundTransaction.class );
		return transactions;
	}
	
	//get the Transaction whose latest execute_date by sorting the Transactions of a customer

	public Date getlatestTransaction(Customer customer){
		Session session = null;	
		try {
			session = this.getSession();
			
			//+ customerId + 
			Query selectClause1 = session.createQuery("select trans.executeDate from FundTransaction trans where trans.customer.id = " + customer.getId() + " and trans.executeDate is not null");

			List<Date> list = selectClause1.list();
			Collections.sort(list);
			
			if (list != null && list.size() > 0) {
				return list.get(list.size()-1);
			}			
			return null;
		} catch (HibernateException e) {
			e.printStackTrace();
			return null;
		} finally {
			if(session != null)	session.close();
		}
	}
	
	public Date getlatestTransaction(LoggedUser customer){
		Session session = null;	
		try {
			session = this.getSession();
			
			//+ customerId + 
			Query selectClause1 = session.createQuery("select trans.executeDate from FundTransaction trans where trans.customer.id = " + customer.getId() + " and trans.executeDate is not null");

			List<Date> list = selectClause1.list();
			Collections.sort(list);
			
			if (list != null && list.size() > 0) {
				return list.get(list.size()-1);
			}			
			return null;
		} catch (HibernateException e) {
			e.printStackTrace();
			return null;
		} finally {
			if(session != null)	session.close();
		}
	}
	
	public List<FundTransaction> getPendingTransactionByCustomer(Customer c) {
		Session session = null;	 
		try {
			session = this.getSession();
			
			Query q = session.createQuery("from FundTransaction trans where trans.status =:s  and trans.customer.id =" + c.getId()).setParameter("s", TransactionStatus.Pending);;
			List<FundTransaction> list = q.list();	
			return list;
		} catch (HibernateException e) {
			e.printStackTrace();
			return null;
		} finally {
			if(session != null)	session.close();
		}
	}
	
	public ArrayList<String> executeTransactions(List<FundTransaction> transactions, TransitionDayForm form) {		
		ArrayList<String> errors = new ArrayList<String>();
		if (transactions == null || transactions.size() == 0) return errors;
		Transaction tx = null;
		Session session = null;
		try {
			session = this.getSession();
			tx = session.beginTransaction();
			for (FundTransaction t : transactions) {
				switch (t.getTransactionType()) {
					case BUY:
						errors.addAll(buyFund(form, t, session));
						if (errors.size() > 0) {
							tx.rollback();
							return errors;
						}
						break;
					case SELL:
						errors.addAll(sellFund(form, t, session));
						if (errors.size() > 0) {
							tx.rollback();
							return errors;
						}
						break;
					case REQUEST_CHECK:
						errors.addAll(checkOperation(-t.getAmount(), form, t, session));
						if (errors.size() > 0) {
							tx.rollback();
							return errors;
						}
						break;
					case DEPOSIT_CHECK:
						errors.addAll(checkOperation(t.getAmount(), form, t, session));
						if (errors.size() > 0) {
							tx.rollback();
							return errors;
						}
						break;
					default:
						return errors;
				}
			}
			
			tx.commit();
		} catch (Exception e) {
			if (tx != null) tx.rollback();
			e.printStackTrace();
			errors.add(e.getMessage());
			return errors;
		} finally {
			if(session != null)	session.close();
		}
		return errors;
	}
	
	public List<Customer> getPendingCustomers() {
		Session session = null;	 
		try {
			session = this.getSession();
			
			Query q = session.createQuery("select distinct trans.customer from FundTransaction trans where trans.executeDate is null");
			
			List<Customer> list = q.list();	
			return list;
		} catch (HibernateException e) {
			e.printStackTrace();
			return null;
		} finally {
			if(session != null)	session.close();
		}
	}
		
	public ArrayList<String> buyFund(TransitionDayForm form, FundTransaction t, Session session) {
		ArrayList<String> errors = new ArrayList<String>();
		try {					
			long shares = getShares(form, t);

			Customer c = t.getCustomer();
			Fund f = t.getFund();
			Position p = positionDAO.getPositionByKey(c, f,session);
			
			// update the position row			
			if (p == null) {
				p = new Position();
				p.setCustomer(c);
				p.setFund(f);
				p.setShares(shares);			
				session.save(p);
			} else {
				p.setShares(p.getShares() + shares);
				session.update(p);
			}
				
			// update the customer cash balance
			long cashBalance = c.getCash() - t.getAmount();
			if (cashBalance < 0) throw new HibernateException("The customer "+ c.getUsername() + " does not have enough shares to sold.");
			c.setCash(cashBalance);		
			session.merge(c);
			
			// update the fundTransaction shares and date
			t.setShares(shares);
			t.setExecuteDate(form.getDate());
			t.setStatus(TransactionStatus.Completed);
			session.update(t);
		} catch (HibernateException e) {
			e.printStackTrace();
			errors.add(e.getMessage());
			return errors;
		} 
		return errors;
	}
	
	public ArrayList<String> sellFund(TransitionDayForm form, FundTransaction t, Session session) {
		ArrayList<String> errors = new ArrayList<String>();
		try {				
			long amount = getAmount(form, t);

			Customer c = t.getCustomer();
			Fund f = t.getFund();
			Position p = positionDAO.getPositionByKey(c, f,session);		
			
			// update the position row
			if (p == null) {
				throw new HibernateException("The fund " + f.getName() + " is not owned by the customer " + c.getUsername() + ".");
			} else {
				long shareBlance = p.getShares() - t.getShares();
				if (shareBlance < 0) throw new HibernateException("The customer "+ c.getUsername() + " does not have enough shares to sold.");
				
				p.setShares(shareBlance);
				session.update(p);
			}
				
			// update the customer cash balance
			c.setCash(c.getCash() + amount);
			session.merge(c);
			
			// update the fundTransaction shares and date
			t.setAmount(amount);
			t.setExecuteDate(form.getDate());
			t.setStatus(TransactionStatus.Completed);
			session.update(t);
		} catch (HibernateException e) {
			e.printStackTrace();
			errors.add(e.getMessage());
			return errors;
		} 	
		return errors;
	}
	
	public ArrayList<String> checkOperation (long checkAmount, TransitionDayForm form, FundTransaction t, Session session) {
		ArrayList<String> errors = new ArrayList<String>();
		try {				
			Customer c = t.getCustomer();
				
			// update the customer cash balance
			long cashBalance = util.Util.addAndCheck(c.getCash(), checkAmount);//c.getCash() + checkAmount;
			if (cashBalance < 0) throw new HibernateException("The customer does not have enough cash balance to request check.");
			c.setCash(cashBalance);
			session.merge(c);
			
			// update the fundTransaction shares and date
			t.setExecuteDate(form.getDate());
			t.setStatus(TransactionStatus.Completed);
			session.update(t);
		} catch (HibernateException e)
		{
			e.printStackTrace();
			errors.add(e.getMessage());
			return errors;
		}	
		catch(ArithmeticException e)
		{
			errors.add("The amount of money to be deposited passed the limitation of system");
		}
		return errors;
	}
	
	public void rejectTransactions(List<FundTransaction> rejectedTransactions)
	{
		Session session = null;
		try
		{
			session = this.getSession();
			Transaction tx = session.beginTransaction();
			for(FundTransaction transaction:rejectedTransactions)
			{
				transaction.setStatus(TransactionStatus.Rejected);
				session.update(transaction);
			}
			tx.commit();
		}
		catch (HibernateException e) {
			e.printStackTrace();
		} finally {
			if(session != null)	session.close();
		}
	}
	
	private long getShares (TransitionDayForm form, FundTransaction t) {
		long price = form.getPrice(t.getFund().getId());		
		double shareDouble = ((double) t.getAmount()) / ((double) price);
		
		return Math.round((shareDouble * 1000.0));
	}
	
	private long getAmount (TransitionDayForm form, FundTransaction t) {
		long price = form.getPrice(t.getFund().getId());		
		double amountDouble = ((double) t.getShares() / 1000.0) * ((double) price / 100.0);
		
		return Math.round((amountDouble * 100.0));
	}
}
